package lk.icta.core.service.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.ELandRuntimeException;
import lk.icta.common.ErrorCodes;
import lk.icta.common.entity.Child;
import lk.icta.core.dao.AlienationDAO;
import lk.icta.core.dao.AlienationMethodDAO;
import lk.icta.core.dao.LandDAO;
import lk.icta.core.dto.AlienationDetails;
import lk.icta.core.entity.*;
import lk.icta.core.service.*;
import lk.icta.user.common.Permission;
import lk.icta.user.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


public class AlienationManagementServiceImpl implements AlienationManagementService {

    private static final Logger logger = LoggerFactory.getLogger(AlienationManagementServiceImpl.class);

    private final AlienationDAO alienationDAO;
    private final AlienationMethodDAO alienationMethodDAO;
    private final MortgageManagementService mortgageManagementService;

    private List<Alienation> alienationTreeList = new ArrayList<Alienation>();

    public AlienationManagementServiceImpl(AlienationDAO alienationDAO, AlienationMethodDAO alienationMethodDAO,
                                           MortgageManagementService mortgageManagementService) {
        this.alienationDAO = alienationDAO;
        this.mortgageManagementService = mortgageManagementService;
        this.alienationMethodDAO = alienationMethodDAO;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addInitialAlienation(Alienation alienation, User user) {
        logger.debug("Adding an initial alienation {} by {}", alienation.getAlienationMethod().getId(),
                user.getUsername());
        if (user.isAuthorized(Permission.INITIAL_ALIENATION_DATA_ENTRY)) {

            alienation.setApproveState(Alienation.ApproveState.DATA_ENTRY);
            alienation.setStatus(Alienation.State.ACTIVE);
            //  alienation = setInitialAlienationInformation(alienation, user);
            alienationDAO.addAlienation(alienation, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to add an Alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addDetailAlienation(Alienation alienation, User user) {
        logger.debug("Adding detail alienation {} by {}", alienation.getAlienationMethod().getId(), user.getUsername());
        if (user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_ENTRY)) {
            if (alienation.getId() != 0) {
                Alienation initAlienation = getAlienationById(alienation.getId(), user);
                /* Archive initial record */
                initAlienation.setStatus(Alienation.State.ARCHIVED);
                alienationDAO.updateAlienation(initAlienation, user);
            }

            alienation.setId(0);
            alienation.getHolder().setId(0);
            alienation.getHolder().getContacts().setId(0);

            if (alienation.getLeaseDetail() != null && alienation.getLeaseDetail().getId() != 0) {
                alienation.getLeaseDetail().setId(0);
            }
            if (alienation.getMortgage() != null && alienation.getMortgage().getId() != 0) {
                alienation.getMortgage().setId(0);
            }
            // alienation.setApproveState(Alienation.ApproveState.DATA_ENTRY);
            alienation.setStatus(Alienation.State.ACTIVE);
            //  alienation = setDetailAlienationInformation(alienation, user);
            alienation.setApproveState(Alienation.ApproveState.PENDING);
            alienationDAO.addAlienation(alienation, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to add detail alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateDetailAlienation(Alienation alienation, User user) {
        logger.debug("Updating detail- alienation {} by {}", alienation.getId(),
                user.getUsername());
        if (user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_MODIFICATION)) {

            alienation.setStatus(Alienation.State.ACTIVE);
            alienation.setApproveState(Alienation.ApproveState.PENDING);

            if (alienation.getId() != 0) {

                Alienation existingAlienation = alienationDAO.getAlienationById(alienation.getId(), user);

                if (existingAlienation != null) {
                    if (Alienation.State.ARCHIVED.equals(existingAlienation.getStatus())) {
                        handleELandRuntimeException("Attempt to update an archived alienation",
                                ErrorCodes.ARCHIVED_ALIENATION_UPDATING);
                    }
                    alienation.getLifeCycleInfo().setCreatedUser(existingAlienation.getLifeCycleInfo().getCreatedUser());
                    alienation.getLifeCycleInfo().setCreatedTimeStamp(existingAlienation.getLifeCycleInfo().getCreatedTimeStamp());
                    alienationDAO.updateAlienation(alienation, user);
                }
            } else {
                alienationDAO.addAlienation(alienation, user);
            }
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to update detail alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteDetailAlienation(Alienation alienation, User user) {
        logger.debug("Deleting detail alienation {} by {}", alienation.getAlienationMethod().getId(), user.getUsername());
        if (user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_MODIFICATION)) {
            Alienation existingAlienation = alienationDAO.getAlienationById(alienation.getId(), user);
            alienation.getLifeCycleInfo().setCreatedUser(existingAlienation.getLifeCycleInfo().getCreatedUser());
            alienation.getLifeCycleInfo().setCreatedTimeStamp(existingAlienation.getLifeCycleInfo().getCreatedTimeStamp());
            alienationDAO.deleteAlienation(alienation, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to delete detail alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteAlienationFromDatabase(long alienationId, User user) {
        logger.debug("Deleting detail alienation {} by {}", alienationId, user.getUsername());
        if (user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_MODIFICATION)) {
            alienationDAO.deleteAlienationFromDatabase(alienationId, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to delete detail alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateAlienation(Alienation alienation, User user) {
        if (!user.isAuthorized(Permission.INITIAL_ALIENATION_DATA_MODIFICATION) ||
                !user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_MODIFICATION)) {
            logger.debug("Does not have permission to update alienation");
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to update alienation",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        } else {
            logger.debug("Updating alienation {}", alienation.getId());
            alienationDAO.updateAlienation(alienation, user);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addMortgage(Alienation alienation, User user) {
        logger.debug("Adding a mortgage to alienation {} by {}", alienation.getAlienationMethod().getId(),
                user.getUsername());
        if (user.isAuthorized(Permission.INITIAL_ALIENATION_DATA_MODIFICATION) ||
                user.isAuthorized(Permission.DETAIL_ALIENATION_DATA_MODIFICATION)) {
            Mortgage mortgage = alienation.getMortgage();
            mortgageManagementService.addMortgage(mortgage, user);
            alienation.setMortgage(mortgage);
            alienationDAO.updateAlienation(alienation, user);
        } else {
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to add mortgage",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void approveAlienation(Alienation alienation, User user) {
        if (!user.isAuthorized(Permission.ALIENATION_APPROVAL)) {
            handleELandRuntimeException(user.getUsername() +
                    " doesn't have permission to approve alienation information", ErrorCodes.UNAUTHORIZED_ACCESS);
        } else {
            Alienation existing = alienationDAO.getAlienationById(alienation.getId(), user);
            if (Alienation.State.ARCHIVED.equals(existing.getStatus())) {
                handleELandRuntimeException("Attempt to approve archived alienation information. [Alienation ID: " +
                        existing.getId() + "]", ErrorCodes.ARCHIVED_ALIENATION_APPROVAL);
            } else if (Alienation.ApproveState.APPROVED.equals(existing.getApproveState())) {
                handleELandRuntimeException("Attempt to approve already approved alienation " + existing.getId(),
                        ErrorCodes.APPROVED_ALIENATION_APPROVAL);
            } else if (Alienation.ApproveState.REJECTED.equals(existing.getApproveState())) {
                handleELandRuntimeException("Attempt to approve already rejected alienation " + existing.getId(),
                        ErrorCodes.REJECTED_ALIENATION_APPROVAL);
            } else if (Alienation.ApproveState.DATA_ENTRY.equals(existing.getApproveState())) {
                handleELandRuntimeException("Attempt to approve incomplete record",
                        ErrorCodes.INCOMPLETE_ALIENATION_APPROVAL);
            } else if (Alienation.ApproveState.PENDING.equals(existing.getApproveState())) {
                logger.debug("Approving alienation {} by {}", existing.getId(), user.getUsername());
                existing.setApproveState(Alienation.ApproveState.APPROVED);
                alienationDAO.updateAlienation(existing, user);
            } else {
                handleELandRuntimeException("Unhandled error occurred", ErrorCodes.ILLEGAL_STATE);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void rejectAlienation(Alienation alienation, User user) {
        if (!user.isAuthorized(Permission.ALIENATION_APPROVAL)) {
            logger.debug("Does not have permission to reject");
            handleELandRuntimeException(user.getUsername() + " doesn't have permission to reject alienation information",
                    ErrorCodes.UNAUTHORIZED_ACCESS);
        } else {
            Alienation existing = alienationDAO.getAlienationById(alienation.getId(), user);
            if (Alienation.State.ARCHIVED.equals(existing.getStatus())) {
                handleELandRuntimeException("Attempt to reject archived alienation information. [Alienation ID: " +
                        existing.getId() + "]", ErrorCodes.ARCHIVED_ALIENATION_REJECTION);
            } else if (Alienation.ApproveState.APPROVED.equals(existing.getApproveState())) {
                handleELandRuntimeException("Attempt to reject already approved alienation " + existing.getId(),
                        ErrorCodes.APPROVED_ALIENATION_REJECTION);
            } else if (Alienation.ApproveState.REJECTED.equals(existing.getApproveState())) {
                handleELandRuntimeException("Attempt to reject already rejected alienation " + existing.getId(),
                        ErrorCodes.REJECTED_ALIENATION_REJECTION);
            } else if (Alienation.ApproveState.DATA_ENTRY.equals(existing.getApproveState())) {
                handleELandRuntimeException("Attempt to reject incomplete record",
                        ErrorCodes.INCOMPLETE_ALIENATION_REJECTION);
            } else if (Alienation.ApproveState.PENDING.equals(existing.getApproveState())) {
                logger.debug("Rejecting alienation {}", existing.getId());
                existing.setApproveState(Alienation.ApproveState.REJECTED);
                alienationDAO.updateAlienation(existing, user);
            } else {
                handleELandRuntimeException("Unhandled error occurred", ErrorCodes.ILLEGAL_STATE);
            }
        }
    }


    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAlienationHistoryByRootLandId(long rootId, User user) {
        logger.debug("Loading alienation history list by {}", user.getUsername());
        List<Alienation> alienationList = alienationDAO.getGrantAlienationHistory(rootId, user);
        alienationList.add(alienationDAO.getActiveAlienationByLandId(rootId, user));
        return alienationList;
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<AlienationDetails> getAlienationHistoryByGrantId(long grantId, User user) {
        logger.debug("Loading alienation history list by {}", user.getUsername());

        List<Alienation> alienationList = alienationDAO.getGrantAlienationHistory(grantId, user);
        alienationList.add(alienationDAO.getAlienationById(grantId, user));

        List<AlienationDetails> alienationDetailsList = new ArrayList<AlienationDetails>();
        for (Alienation a : alienationList) {
            AlienationDetails newAlienation = new AlienationDetails();
            newAlienation.setId(a.getId());
            newAlienation.setReferenceNo(a.getReferenceNo());
            if (a.getImmediateParent() != null) {
                newAlienation.setImmediateParentId(a.getImmediateParent().getId());
            } else {
                newAlienation.setImmediateParentId(0);
            }
            if (a.getLand() != null) {
                newAlienation.setLandId(a.getLand().getId());
                newAlienation.setLandExtent(a.getLand().getLandExtent());
            }
            alienationDetailsList.add(newAlienation);
        }
        return alienationDetailsList;
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public Alienation getAlienationById(Long id, User user) {
        logger.debug("getAlienationById {}", id);
        Alienation alienation = alienationDAO.getAlienationById(id, user);
        return alienation;
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAllActiveAlienation(User user) {
        logger.debug("List all active alienation by {}", user);
        return alienationDAO.getAllActiveAlienation(user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAlienationByLand(long landId, User user) {
        logger.debug("getAlienationByLandId {}", landId);
        List<Alienation> alienation = alienationDAO.getAlienationByLand(landId, user);
        return alienation;
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public Alienation getActiveAlienationByLandId(long landId, User user) {
        logger.debug("getActiveAlienationByLandId {}", landId);
        Alienation alienation = alienationDAO.getActiveAlienationByLandId(landId, user);
        if (alienation != null && alienation.getHolder().getChildren() != null) {
            // alienation.setHolder(alienation.getHolder());
            alienation.getHolder().setChildren(alienation.getHolder().getChildren());

            for (Child c : alienation.getHolder().getChildren()) {
                logger.debug("Childrn name is -- {}", c.getFullName());
            }
        }

        return alienation;
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public Alienation getPendingAlienationByLandId(long landId, User user) {
        logger.debug("getPendingAlienationByLandId {}", landId);
        Alienation alienation = alienationDAO.getPendingAlienationByLandId(landId, user);
        if (alienation != null && alienation.getHolder().getChildren() != null) {
            // alienation.setHolder(alienation.getHolder());
            alienation.getHolder().setChildren(alienation.getHolder().getChildren());

            for (Child c : alienation.getHolder().getChildren()) {
                logger.debug("Childrn name is -- {}", c.getFullName());
            }
        }

        return alienation;
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAllChildNodesByParentId(long parentId, User user) {
        logger.debug("Listing Child Alienation of {} by {}", parentId, user.getUsername());
        alienationTreeList.clear();
        return getChildNodes(alienationDAO.getAlienationById(parentId, user), user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAlienationByAlienationMethod(int alienationMethod, User user) {
        logger.debug("Listing alienation by alienation method {} by {}", alienationMethod, user.getUsername());
        return alienationDAO.getAlienationByAlienationMethod(alienationMethod, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAllAlienationByAlienationMethod(int alienationMethod, User user) {
        logger.debug("Listing all alienation by alienation method {} by {}", alienationMethod, user.getUsername());
        return alienationDAO.getAllAlienationByAlienationMethod(alienationMethod, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public Map<Integer, String> getAllAlienationMethods(String language, User user) {
        //logger.debug("Listing alienation methods in {} by {}", language, user.getUsername());
        return alienationMethodDAO.getAllAlienationMethods(language, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public Map<Integer, String> getAllAlienationMethods(String language) {
        logger.debug("Listing alienation methods in {}", language);
        return alienationMethodDAO.getAllAlienationMethods(language);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public Alienation getActiveAlienationByPermitNo(String permitNo, User user) {
        Alienation alienation = alienationDAO.getActiveAlienationByReferenceNo(permitNo);
        if (alienation != null) {
            if (alienation.getAlienationMethod().getId() != AppConstants.LAND_PERMIT) {
                return null;
            }
        }
        return alienation;
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public Alienation getActiveApprovedAlienationByPermitNo(String permitNo, User user) {
        Alienation alienation = alienationDAO.getActiveApprovedAlienationByReferenceNo(permitNo);
        if (alienation != null) {
            if (alienation.getAlienationMethod().getId() != AppConstants.LAND_PERMIT) {
                return null;
            }
        }
        return alienation;
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public Boolean isExistsReference(String referenceNo, User user) {
        logger.debug("Check for duplicate reference {} by {}", referenceNo, user.getUsername());
        return alienationDAO.isExistsReference(referenceNo);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addLandAlienationMetadata(LandAssociation LAMMetadata, User user) {
        alienationDAO.addLandAlienationMetadata(LAMMetadata, user);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public AlienationMethod getAlienationMethodById(int id, User user) {
        return alienationMethodDAO.getAlienationMethodById(id, user);
    }

    private void handleELandRuntimeException(String message, int code) {
        logger.error(message);
        throw new ELandRuntimeException(message, code);
    }

    private List<Alienation> getChildNodes(Alienation alienation, User user) {
        List<Alienation> children = alienationDAO.getChildAlienationNodesByParentId(alienation.getId(), user);
        for (Alienation child : children) {
            getChildNodes(child, user);
            alienationTreeList.add(child);
        }
        return alienationTreeList;
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<Alienation> getAllAlienation(User user) {
        logger.debug("List all  alienation by {}", user);
        return alienationDAO.getAllAlienation(user);
    }

    public List<Alienation> getPendingAlienationsByAlienationMethod(int alienationId, User user) {
        return alienationDAO.getPendingAlienationByAlienationMethod(alienationId, user);
    }

    @Override
    public List<Alienation> searchPermitLands(User user) {
        return alienationDAO.searchPermit(user);
    }
}
